package com.baijia.util;

import com.google.common.collect.Maps;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import com.mashape.unirest.http.Unirest;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class RestUtils {

    private static final Logger logger = LoggerFactory.getLogger(RestUtils.class);

    public enum RestMethod {
        GET, POST
    }

    private static final String URL_SPLIT = "/";
    private static final String SIGN_SPLIT = "-";
    private static final String DEF_CHARSET = "UTF-8";
    private static final String HMAC_SHA1 = "HmacSHA1";

    private static final byte[] LOGIN_LOCK = new byte[0];
    private static final long EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7;

    public static String APP_ID;
    public static String APP_KEY;

    public static String DEF_BASE_URL;

    public static volatile String authToken = null;
    public static volatile long loginTime = -1;

    private static final TypeToken<RestfulResult<Map<String, Object>>> DEF_DATA_TYPE =
        new TypeToken<RestfulResult<Map<String, Object>>>() {

            /**
            * 
            */
            private static final long serialVersionUID = 3693076384489731845L;

        };

    public RestUtils(String appId, String appKey, String baseUrl) {
        APP_ID = appId;
        APP_KEY = appKey;
        DEF_BASE_URL = baseUrl;
    }
    
    public static void init(String appId, String appKey, String baseUrl){
        APP_ID = appId;
        APP_KEY = appKey;
        DEF_BASE_URL = baseUrl;
    }

    private static final Gson gson = new Gson();

    /**
     * 调用主站rest接口，一般使用{@link #restWithDefPara(String, String, List, Map, Map, TypeToken)}即可
     * 
     * @param restMethod 接口调用方法，POST/GET
     * @param baseUrl 服务的基础URL，发送短信接口http://test-service.genshuixue.com/sms/send中的http://test-service.genshuixue.com/
     * @param controller 服务的controller，发送短信接口http://test-service.genshuixue.com/sms/send中的sms
     * @param action 服务的action，发送短信接口http://test-service.genshuixue.com/sms/send中的send
     * @param urlVarList URL中的参数，即URL中?后的部分
     * @param params POST请求参数
     * @param headers 请求headers
     * @param dataType 返回的数据类型
     * @return
     * @throws Exception
     */
    public static <T> RestfulResult<T> rest(RestMethod restMethod, String baseUrl, String controller, String action,
        List<String> urlVarList, Map<String, Object> params, Map<String, String> headers,
        TypeToken<RestfulResult<T>> dataType) throws Exception {
        // 创建url
        StringBuilder loginUrl = new StringBuilder();
        loginUrl.append(baseUrl);
        loginUrl.append(URL_SPLIT).append(controller);
        loginUrl.append(URL_SPLIT).append(action);

        // 生成签名
        String signData = createSignData(restMethod.toString(), controller, action, urlVarList, params);
        String sign = getSignature(signData.getBytes(DEF_CHARSET), APP_KEY.getBytes(DEF_CHARSET));

        // 设置签名
        params.put("sign", sign);

        // 发送请求
        String resultJson = null;
        switch (restMethod) {
            case POST:
                resultJson = Unirest.post(loginUrl.toString()).headers(headers).fields(params).asString().getBody();
                break;
            case GET:
                resultJson =Unirest.get(loginUrl.toString()).headers(headers).queryString(params).asString().getBody();
                break;
            default:
                throw new Exception("不支持的REST方法");
        }
        logger.debug("resultJson＝" + resultJson);

        // 解析结果
        RestfulResult<T> result = gson.fromJson(resultJson, dataType.getType());

        return result;
    }

    /**
     * 使用默认参数调用主站rest接口，POST方法，默认服务地址，会自动增加必须的app_id，auth_token和timestamp参数，参数参见
     * {@link #rest(RestMethod, String, String, String, List, Map, Map, TypeToken)}
     * 
     * @param controller
     * @param action
     * @param urlVarList
     * @param params
     * @param headers
     * @param dataType
     * @return
     * @throws Exception
     */
    public static <T> RestfulResult<T> restWithDefPara(String controller, String action, List<String> urlVarList,
        Map<String, Object> params, Map<String, String> headers, TypeToken<RestfulResult<T>> dataType) throws Exception {
        return restWithAuthtoken(RestMethod.POST, DEF_BASE_URL, controller, action, urlVarList, params, headers,
            dataType);
    }

    /**
     * 调用主站的接口，会自动增加必须的app_id，auth_token和timestamp参数，参数参见
     * {@link #rest(RestMethod, String, String, String, List, Map, Map, TypeToken)}
     * 
     * @param restMethod
     * @param baseUrl
     * @param controller
     * @param action
     * @param urlVarList
     * @param params
     * @param headers
     * @param dataType
     * @return
     * @throws Exception
     */
    public static <T> RestfulResult<T> restWithAuthtoken(RestMethod restMethod, String baseUrl, String controller,
        String action, List<String> urlVarList, Map<String, Object> params, Map<String, String> headers,
        TypeToken<RestfulResult<T>> dataType) throws Exception {
        String authToken = getAuthToken();
        if (StringUtils.isBlank(authToken)) {
            RestfulResult<T> createResult = new RestfulResult<>();
            createResult.setCode(RestfulResult.CODE_FAIL);
            createResult.setMsg("验证失败");
            return createResult;
        }
        if (params == null) {
            params = Maps.newHashMapWithExpectedSize(3);
        }
        params.put("app_id", APP_ID);
        params.put("auth_token", authToken);
        params.put("timestamp", String.valueOf(System.currentTimeMillis()));
        return rest(restMethod, baseUrl, controller, action, urlVarList, params, headers, dataType);
    }

    /**
     * 获取AuthToken TODO 修改为从memcached获取，避免重复获取
     * 
     * @return
     * @throws Exception
     */
    public static String getAuthToken() throws Exception {
        synchronized (LOGIN_LOCK) {
            if (loginTime - System.currentTimeMillis() < EXPIRE_TIME && authToken != null) {
                return authToken;
            }
            String result = null;
            try {
                String controller = "auth";
                String action = "login";
                // 设置参数
                Map<String, Object> params = Maps.newHashMapWithExpectedSize(3);
                params.put("app_id", APP_ID);
                params.put("app_key", APP_KEY);
                params.put("timestamp", String.valueOf(System.currentTimeMillis()));
                RestfulResult<Map<String, Object>> loginResult =
                    rest(RestMethod.POST, DEF_BASE_URL, controller, action, null, params, null, DEF_DATA_TYPE);
                if (loginResult == null) {
                    return null;
                }
                if (loginResult.getCode() != RestfulResult.CODE_SUCCESS) {
                    throw new Exception(loginResult.getMsg());
                }
                result = loginResult.getData().get("auth_token").toString();
                return result;
            } finally {
                loginTime = System.currentTimeMillis();
                authToken = result;
            }
        }
    }

    /**
     * 生成签名字符串
     * 
     * @param restMethod
     * @param controllerName
     * @param actionName
     * @param urlVarList
     * @param params
     * @return
     */
    private static String createSignData(String restMethod, String controllerName, String actionName,
        List<String> urlVarList, Map<String, Object> params) {
        StringBuilder signData = new StringBuilder();
        signData.append(restMethod.toLowerCase());
        signData.append(SIGN_SPLIT).append(controllerName);
        signData.append(SIGN_SPLIT).append(actionName);

        if (urlVarList != null && urlVarList.isEmpty() == false) {
            for (String urlVar : urlVarList) {
                if (urlVar == null) {
                    signData.append(SIGN_SPLIT).append("");
                } else {
                    signData.append(SIGN_SPLIT).append(urlVar);
                }

            }
        }

        if (params != null && params.isEmpty() == false) {
            String[] paraNames = new String[params.size()];
            Arrays.sort(params.keySet().toArray(paraNames));
            for (String paraName : paraNames) {
                String value =String.valueOf(params.get(paraName));
                if (value == null) {
                    value = "";
                }
                signData.append(SIGN_SPLIT).append(value);
            }
        }
        return signData.toString();
    }

    /**
     * 生成签名数据
     * 
     * @param data 待加密的数据
     * @param key 加密使用的key
     * @return 生成BASE64编码的字符串
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     */
    private static String getSignature(byte[] data, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException {
        SecretKeySpec signingKey = new SecretKeySpec(key, HMAC_SHA1);
        Mac mac = Mac.getInstance(HMAC_SHA1);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(data);
        return Base64.encodeBase64String(rawHmac);
    }
}
